﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using SIR.Common.Condition;
using SIR.Entities.Implementations;
using System.Linq;

namespace SIR.DAL.Tests
{
	[TestClass]
	public class DataServerTest
	{
		// DB connection string
		internal static string _validConnectionString = @"Data Source=(local);Initial Catalog=SIR.Database;Integrated Security=True;Pooling=False;Connect Timeout=30";
		private static object _lock = new object();

		[TestCleanup]
		public void Disconnect()
		{
			// Disconnect on clean up
			DataServer.Disconnect();
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Connect, 1, typeof(DataServer))]
		public void Connect_ConnectionStringRequired()
		{
			var errorMessage = string.Empty;
			bool isConnected = false;
			lock (_lock)
			{
				// connect with no connection string
				isConnected = DataServer.Connect(string.Empty, out errorMessage);
				DataServer.Disconnect();
			}
			// verify connection string is required
			Assert.AreEqual("Connection string is required", errorMessage);
			Assert.IsFalse(isConnected);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Connect, 2, typeof(DataServer))]
		public void Connect_InvalidConnectionString()
		{
			var errorMessage = string.Empty;
			bool isConnected = false;
			lock (_lock)
			{
				// connect with invalid string
				isConnected = DataServer.Connect("Invalid-Connection-String", out errorMessage);
				DataServer.Disconnect();
			}
			// verify message and not connected
			Assert.AreEqual("Invalid connection string", errorMessage);
			Assert.IsFalse(isConnected);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Connect, 3, typeof(DataServer))]
		public void Connect_FailedToConnect()
		{
			var errorMessage = string.Empty;
			bool isConnected = false;
			lock (_lock)
			{
				// connect with valid but wrong string
				isConnected = DataServer.Connect(@"Data Source=(local);Initial Catalog=FakeDB;Integrated Security=True;Pooling=False;Connect Timeout=30", out errorMessage);
				DataServer.Disconnect();
			}
			// verify message and not connected
			Assert.IsFalse(isConnected);
			Assert.AreEqual("Could not connect to the server", errorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Connect, 4, typeof(DataServer))]
		public void Connect_Successful()
		{
			var errorMessage = string.Empty;
			bool isConnected = false;
			lock (_lock)
			{
				// valid connection
				isConnected = DataServer.Connect(_validConnectionString, out errorMessage);
				DataServer.Disconnect();
			}
			// verify message is empty and connected
			Assert.IsTrue(string.IsNullOrWhiteSpace(errorMessage));
			Assert.IsTrue(isConnected);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Connect, 5, typeof(DataServer))]
		public void Connect_AlreadyConnected()
		{
			var errorMessage = string.Empty;
			bool isConnected = false;
			lock (_lock)
			{
				// validate valid connection
				isConnected = DataServer.Connect(_validConnectionString, out errorMessage);
				Assert.IsTrue(string.IsNullOrWhiteSpace(errorMessage));
				Assert.IsTrue(isConnected, "initial connection");
				// try to connect again
				isConnected = DataServer.Connect(_validConnectionString, out errorMessage);
				// disconnect
				DataServer.Disconnect();
			}
			// validate connected but error message shows that it was already connected
			Assert.AreEqual("A connection to the database has already been established", errorMessage);
			Assert.IsTrue(isConnected);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Read, 1, typeof(DataServer))]
		public void Read_NoConnection()
		{
			lock (_lock)
			{
				// Attempt find without a connection
				var results = DataServer.Find<User>(null);
				// Verify results
				Assert.IsNotNull(results);
				Assert.IsFalse(results.WasSuccessful);
				Assert.AreEqual("No connection has been established", results.ErrorMessage);
				Assert.AreEqual(0, results.Results.Count);
			}
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Read, 2, typeof(DataServer))]
		public void Read_Invalid()
		{
			DataServerResults<User> results;
			lock (_lock)
			{
				// connect and read from user with invalid condition
				DataServer.Connect(_validConnectionString);
				results = DataServer.Find<User>(new Predicate("foo", 1, PredicateOperator.EqualTo));
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(results);
			Assert.IsFalse(results.WasSuccessful);
			Assert.AreEqual("Condition was invalid", results.ErrorMessage);
			Assert.IsNotNull(results.Results);
			Assert.AreEqual(0, results.Results.Count());
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Read, 3, typeof(DataServer))]
		public void Read_NoResults()
		{
			DataServerResults<User> results;
			lock (_lock)
			{
				// Read a user that doesn't exist
				DataServer.Connect(_validConnectionString);
				results = DataServer.Find<User>(new Predicate("Email", "not-a-real-email", PredicateOperator.EqualTo));
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(results);
			Assert.IsTrue(results.WasSuccessful);
			Assert.IsTrue(string.IsNullOrWhiteSpace(results.ErrorMessage));
			Assert.IsNotNull(results.Results);
			Assert.AreEqual(0, results.Results.Count);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Read, 4, typeof(DataServer))]
		public void Read_WithResults()
		{
			DataServerResults<User> results;
			lock (_lock)
			{
				// Read all users from the database
				DataServer.Connect(_validConnectionString);
				results = DataServer.Find<User>();
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(results);
			Assert.IsTrue(results.WasSuccessful);
			Assert.IsTrue(string.IsNullOrWhiteSpace(results.ErrorMessage));
			Assert.IsNotNull(results.Results);
			Assert.AreEqual(11, results.Results.Count);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Insert, 1, typeof(DataServer))]
		public void Insert_NoConnection()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// attempt insert without connection
				result = DataServer.Save<User>(null);
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.AreEqual("No connection has been established", result.ErrorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Insert, 2, typeof(DataServer))]
		public void Insert_Invalid()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// Cannot insert a null object
				DataServer.Connect(_validConnectionString);
				result = DataServer.Save<User>(null);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.AreEqual("Invalid save call, something must be provided to save", result.ErrorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Insert, 3, typeof(DataServer))]
		public void Insert_SingleRecord()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// Insert a new record and then delete it
				DataServer.Connect(_validConnectionString);
				result = DataServer.Save<User>(new User() { Email = "testInsertSingleRecord@testing.com", Password = "fake" });
				var user = result.NewValue;
				user.IsDeleted = true;
				DataServer.Save<User>(user);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsTrue(result.WasSuccessful);
			Assert.IsNull(result.OldValue);
			Assert.IsNotNull(result.NewValue);
			Assert.IsTrue(string.IsNullOrWhiteSpace(result.ErrorMessage));
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Update, 1, typeof(DataServer))]
		public void Update_NoConnection()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// Attempt update without connection
				result = DataServer.Save<User>(null);
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.AreEqual("No connection has been established", result.ErrorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Update, 2, typeof(DataServer))]
		public void Update_Invalid()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// Cannot save a null object
				DataServer.Connect(_validConnectionString);
				result = DataServer.Save<User>(null);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.AreEqual("Invalid save call, something must be provided to save", result.ErrorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Update, 3, typeof(DataServer))]
		public void Update_NoRecords()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				DataServer.Connect(_validConnectionString);
				// Find a user and save it back
				var user = DataServer.Find<User>(new Predicate("Email", "test@email.com", PredicateOperator.EqualTo)).Results.FirstOrDefault();
				result = DataServer.Save<User>(user);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsTrue(result.WasSuccessful);
			Assert.IsNotNull(result.OldValue);
			Assert.IsNotNull(result.NewValue);
			Assert.IsTrue(string.IsNullOrWhiteSpace(result.ErrorMessage));
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Update, 4, typeof(DataServer))]
		public void Update_SingleRecord()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				DataServer.Connect(_validConnectionString);
				// Find a user an save it back
				var user = DataServer.Find<User>(new Predicate("Email", "test@email.com", PredicateOperator.EqualTo)).Results.FirstOrDefault();
				result = DataServer.Save<User>(user);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsTrue(result.WasSuccessful);
			Assert.IsNotNull(result.OldValue);
			Assert.IsNotNull(result.NewValue);
			Assert.IsTrue(string.IsNullOrWhiteSpace(result.ErrorMessage));
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Delete, 1, typeof(DataServer))]
		public void Delete_NoConnection()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// attempt save without a connection
				result = DataServer.Save<User>(null);
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.AreEqual("No connection has been established", result.ErrorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Delete, 2, typeof(DataServer))]
		public void Delete_Invalid()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// Cannot delete a null object
				DataServer.Connect(_validConnectionString);
				result = DataServer.Save<User>(null);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.AreEqual("Invalid save call, something must be provided to save", result.ErrorMessage);
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Delete, 3, typeof(DataServer))]
		public void Delete_NoRecords()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				// Validate no results on delete of an object that wasn't saved
				DataServer.Connect(_validConnectionString);
				var user = new User() { IsDeleted = true, IsNew = false };
				result = DataServer.Save<User>(user);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsFalse(result.WasSuccessful);
			Assert.IsNotNull(result.OldValue);
			Assert.IsNotNull(result.NewValue);
			Assert.IsTrue(string.IsNullOrWhiteSpace(result.ErrorMessage));
		}

		[TestMethod]
		[SIRTestCategory(typeof(Area.DataAccessLayer), Area.DataAccessLayer.Delete, 4, typeof(DataServer))]
		public void Delete_SingleRecord()
		{
			DataServerResult<User> result;
			lock (_lock)
			{
				DataServer.Connect(_validConnectionString);
				// Create new user
				var user = new User() { Email = "Delete_SingleRecord", Password = "" };
				result = DataServer.Save<User>(user);
				user = result.NewValue;
				Assert.IsNotNull(user, "User to be deleted is was created");
				user.IsDeleted = true;
				// Delete the new user
				result = DataServer.Save<User>(user);
				DataServer.Disconnect();
			}
			// Verify results
			Assert.IsNotNull(result);
			Assert.IsTrue(result.WasSuccessful);
			Assert.IsNotNull(result.OldValue);
			Assert.IsNull(result.NewValue);
			Assert.IsTrue(string.IsNullOrWhiteSpace(result.ErrorMessage));
		}
	}
}
